/*
 * Copyright (c) 2006-2023, RT-Thread Development Team
 *
 * OLED引脚： SDA -> PB7  SCL -> PB6
 * 光敏传感器引脚：   adc1: AO -> PA0
 *              adc2: A0 -> PA1
 *              adc3: A0 -> PA2
 * LED灯引脚：PA3 PA4 PA5
 *
 * Change Logs:
 * Date           Author       Notes
 * 2023-06-07     洪家团    first version
 */

#include <rtthread.h>
#include <board.h>
#include <stdio.h>
#include "light.h"
#include "oled.h"
#include "led.h"

//定义线程栈大小
ALIGN(RT_ALIGN_SIZE)
#define THREAD_STACK_SIZE 4096

//定义线程参数
#define THREAD_PRIORITY 6
#define THREAD_TIMESLICE 5

/* 定 时 器 的 TICK 宏  定 义，因为Tick被系统定义成1ms，所以5000则是5000ms */
#define CIRCULATE_TIMER_TICK 5000


//声明函数
void thread_init(void);
void mutex_init(void);
void timer_init(void);
void mail_init(void);
static void light1_thread_entry(void *parameter);
static void light2_thread_entry(void *parameter);
static void light3_thread_entry(void *parameter);
static void oled_thread_entry(void *parameter);
static void compute_thread_entry(void *parameter);
static void compute_timeout(void *parameter);
void my_delay(int time);


//定义全局变量光照强度
unsigned int g_light_value1;
unsigned int g_light_value2;
unsigned int g_light_value3;
unsigned int g_light_average;

/* 指 向 互 斥 量 的 指 针 */
static rt_mutex_t dynamic_mutex = RT_NULL;

//定义线程标识符
static rt_thread_t light1_thread = RT_NULL;
static rt_thread_t light2_thread = RT_NULL;
static rt_thread_t light3_thread = RT_NULL;
static rt_thread_t oled_thread = RT_NULL;
static rt_thread_t compute_thread = RT_NULL;

/* 定 时 器 的 控 制 块 */
static rt_timer_t compute_timer;  //定时器的控制块

/* 邮 箱 控 制 指针 */
static rt_mq_t msgqueue = RT_NULL;


//用于提醒管理的消息队列的信息
static char mb_led_on_str[] = "LED:ON";
static char mb_led_off_str[] = "LED:OFF";


/**
 * @brief    程序的主入口函数 也是第一个执行的程序
 * @note     完成硬件初始化、任务、 消息队列、互斥锁的创建
 * @param    无传入的参数
 */
int main(void)
{
    //硬件初始化
    light_init();
    oled_init();

    //系统初始化
    mail_init();
    mutex_init();
    thread_init();
    timer_init();

    //主函数进入死循环 全部任务在线程中完成
    while(1)
    {

    }
    return RT_EOK;
}


/**
 * @brief    光照强度获取线程1执行函数
 * @note     先检查是否能得到互斥锁，不能则进入休眠状态，
 *           在获取到光照值之后让出线程100ms，这样做是为了不打断OLED线程
 * @param    无传入的参数
 */
static void light1_thread_entry(void *parameter)
{
    while(1)
    {
        //检查是否能得到互斥量 不能则进入休眠
        rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);

        //获取光照强度1
        get_light_value1();

        rt_thread_mdelay(100);

        //释放互斥量
        rt_mutex_release(dynamic_mutex);
    }
}


/**
 * @brief    光照强度获取线程2执行函数
 * @note     先检查是否能得到互斥锁，不能则进入休眠状态，
 *           在获取到光照值之后让出线程100ms，这样做是为了不打断OLED线程
 * @param    无传入的参数
 */
static void light2_thread_entry(void *parameter)
{
    while(1)
    {
        //检查是否能得到互斥量 不能则进入休眠
        rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);

        //获取光照强度2
        get_light_value2();

        rt_thread_mdelay(100);

        //释放互斥量
        rt_mutex_release(dynamic_mutex);
    }
}


/**
 * @brief    光照强度获取线程3执行函数
 * @note     先检查是否能得到互斥锁，不能则进入休眠状态，
 *           在获取到光照值之后让出线程100ms，这样做是为了不打断OLED线程
 * @param    无传入的参数
 */
static void light3_thread_entry(void *parameter)
{
    while(1)
    {
        //检查是否能得到互斥量 不能则进入休眠
        rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);

        //获取光照强度值3
        get_light_value3();

        rt_thread_mdelay(100);

        //释放互斥量
        rt_mutex_release(dynamic_mutex);
    }
}

/**
 * @brief    OLED显示线程函数
 * @note     先检查能否获得互斥锁，不能则进入休眠
  *                  调用oled_show函数来进行显示，定义在oled.c内
  *                  同时该函数还要负责提醒管理者LED灯的状态
  *                  通过消息队列来获取到LED灯的状态
  *                  光照值则是直接使用全局变量来显示
 * @param    无传入的参数
 */
static void oled_thread_entry(void *parameter)
{
    char buf[20];
    while(1)
    {
        //检查是否能得到互斥量 不能则进入休眠
        rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);

        //以非阻塞的方式读取消息队列中LED的值
        rt_mq_recv(msgqueue, buf, sizeof(buf), RT_WAITING_NO);

        oled_show(buf);

        //释放互斥量
        rt_mutex_release(dynamic_mutex);
    }
}

/**
 * @brief    计算执行线程函数
 * @note     该线程为系统中最高优先级的线程，因此不会被其他线程打断
 *           即在计算光照强度的时候，光照强度的值不会被更新
 *           取3个光照值的平均值，然后判断值的大小是否进行开灯
 *           通过消息队列向显示函数发送LED的状态
 *           执行完之后进行软延迟3s左右，然后该线程进入休眠，5s后进行唤醒
 *           该函数的周期为5s
 * @param    无传入的参数
 */
static void compute_thread_entry(void *parameter)
{
    while(1)
    {
        //求光照平均值
        g_light_average = (g_light_value1 + g_light_value2 + g_light_value3)/3;

        //判断平均值 是否进行开灯操作 同时判断光照强度 给予不同颜色LED灯
        if(g_light_average > 2500)
        {
            led_set(LED_OFF);
            led_set(LED_ON_B);
            //向消息队列写入LED灯的状态
            rt_mq_send(msgqueue, mb_led_on_str, sizeof(mb_led_on_str));
        }
        else if (g_light_average > 2000)
        {
            led_set(LED_OFF);
            led_set(LED_ON_G);
            //向消息队列写入LED灯的状态
            rt_mq_send(msgqueue, mb_led_on_str, sizeof(mb_led_on_str));
        }
        else if (g_light_average > 1500)
        {
            led_set(LED_OFF);
            led_set(LED_ON_R);
            //向消息队列写入LED灯的状态
            rt_mq_send(msgqueue, mb_led_on_str, sizeof(mb_led_on_str));
        }
        else
        {
            led_set(LED_OFF);
            rt_mq_send(msgqueue, mb_led_off_str, sizeof(mb_led_off_str));
        }

        //函数内延迟3s
        my_delay(3000);

        //线程休眠5s
        rt_thread_mdelay(5000);
    }
}


/**
 * @brief    单次定时器超时函数
 * @note     从系统开始运行到5s时间，触发该定时器中断
 *           在中断服务函数里面启动线程计算函数，进行第一次光照值的计算
 * @param    无传入的参数
 */
static void compute_timeout(void *parameter)
{
    /* 如 果 获 得 线 程 控 制 块， 启 动 这 个 线 程 */
    if (compute_thread != RT_NULL)
    rt_thread_startup(compute_thread);
}


/**
 * @brief    线程初始化函数
 * @note     完成3个光照获取线程、OLED显示线程、计算线程的创建
 * @param    无传入的参数
 */
void thread_init(void)
{
    //创建获取光照强度1线程
    light1_thread = rt_thread_create("thread1",
                            light1_thread_entry,
                            RT_NULL,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY,
                            THREAD_TIMESLICE);
    /* 如 果 获 得 线 程 控 制 块， 启 动 这 个 线 程 */
    if (light1_thread != RT_NULL)
    rt_thread_startup(light1_thread);

    //创建获取光照强度2线程
    light2_thread = rt_thread_create("thread2",
                            light2_thread_entry,
                            RT_NULL,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY,
                            THREAD_TIMESLICE);
    /* 如 果 获 得 线 程 控 制 块， 启 动 这 个 线 程 */
    if (light2_thread != RT_NULL)
    rt_thread_startup(light2_thread);

    //创建获取光照强度3线程
    light3_thread = rt_thread_create("thread3",
                            light3_thread_entry,
                            RT_NULL,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY,
                            THREAD_TIMESLICE);
    /* 如 果 获 得 线 程 控 制 块， 启 动 这 个 线 程 */
    if (light3_thread != RT_NULL)
    rt_thread_startup(light3_thread);

    //创建OLED显示线程
    oled_thread = rt_thread_create("thread4",
                            oled_thread_entry,
                            RT_NULL,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY,
                            THREAD_TIMESLICE);
    /* 如 果 获 得 线 程 控 制 块， 启 动 这 个 线 程 */
    if (oled_thread != RT_NULL)
    rt_thread_startup(oled_thread);

    //创建计算线程
    compute_thread = rt_thread_create("thread5",
                            compute_thread_entry,
                            RT_NULL,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY-1,
                            THREAD_TIMESLICE);
}

/**
 * @brief    互斥量初始化函数
 * @note     创建了一个动态互斥量，该互斥量用于光照传感器和OLED之间的同步
 *                              因为光照传感器的光照值获取和OLED的光照值显示属于典型的生产者-消费者问题
 *                              因此使用互斥量进行操作
 * @param    无传入参数
 */
void mutex_init(void)
{
    /* 创 建 一 个 动 态 互 斥 量 */
    dynamic_mutex = rt_mutex_create("dmutex", RT_IPC_FLAG_FIFO);
    if (dynamic_mutex == RT_NULL)
    {
        rt_kprintf("create dynamic mutex failed.\n");
    }

}


/**
 * @brief    定时器初始化函数
 * @note     完成了单次定时器的初始化，该定时器用于启动计算线程
 * @param    定时5s
 */
void timer_init(void)
{
    compute_timer = rt_timer_create("compute_timer",
                                      compute_timeout,
                                      RT_NULL,
                                      CIRCULATE_TIMER_TICK,
                                      RT_TIMER_FLAG_ONE_SHOT);
    /* 启 动 定 时 器  */
    if (compute_timer != RT_NULL) rt_timer_start(compute_timer);

}

/**
 * @brief    消息队列初始化函数
 * @note     该消息队列用于计算线程和显示线程之间通信
 *                    将开关灯的状态信息发送给显示线程以提醒管理者
 *
 * @param
 */
void mail_init(void)
{
    msgqueue = rt_mq_create("msgqueue",
                50, /* 消息最大长度 */
                10, /* 消息队列最大容量 */
                RT_IPC_FLAG_FIFO);/* FIFO模式 */
}


/**
 * @brief    软延迟函数
 * @note     因为rtthread系统没有提供软延迟函数，因此自己写了个
 *           经过估测，传入值为1000时延迟的时间大约是1s
 * @param    int time     毫秒延迟数
 */
void my_delay(int time)
{
    for(int i=0;i<time;i++)
        for(int j=0;j<10000;j++);
}
